/* Copyright 2008. All rights reserved. */
package org.bling.process;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.bling.Action;
import org.bling.ApplicationController;
import org.bling.Partial;
import org.bling.Render;
import org.bling.parser.BlingDocument;
import org.bling.parser.BlingParserException;
import org.bling.process.context.ApplicationContext;
import org.bling.process.render.BlingRenderException;
import org.bling.process.render.Renderer;
import org.bling.process.render.SingleDocumentInterfaceRenderer;
import org.bling.util.Constants;
import org.bling.util.Util;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.gorm.orm.GormStandalone;

public class RequestProcessor {

	private static final Logger LOG = LogManager
			.getLogger(RequestProcessor.class);
	private static final Renderer renderer = new SingleDocumentInterfaceRenderer();

	public Renderer getRenderer() {
		return renderer;
	}
	
	public void process(Request request) throws BlingProcessingException,
			BlingRenderException, BlingParserException {
		LOG.debug("Processing Request: " + request);

		Command command = new Command(request.getPath());

		Response response = performAction(command);

		if (response != null) {
			LOG.debug("Parsing Document: " + response.path);
			BlingDocument doc = loadDocument(response);

			renderer.render(request, response, doc);
		}
	}

	private BlingDocument loadDocument(Response response)
			throws BlingProcessingException {
		String filename = response.path;
		InputStream stream = ClassLoader.getSystemResourceAsStream(filename
				+ ".bling");

		if (stream == null) {
			throw new BlingProcessingException(
					"File Not Found - Unable to load Bling document: "
							+ filename);
		}

		try {
			LOG.debug("Opening bling document: " + filename);

			/*
			 * // FIXME: It is difficult to convert from a BufferedInputStream
			 * to ByteArrayInputStream // for now, I'm pulling the data into a
			 * buffer final byte[] buffer = new byte[stream.available()];
			 * stream.read(buffer); BlingDocument doc = new BlingDocument(new
			 * ByteArrayInputStream(buffer));
			 */
			BlingDocument doc = new BlingDocument(stream);

			if (doc == null) {
				throw new BlingProcessingException(
						"Unable to load Bling document: " + filename);
			}

			return doc;
		} catch (IOException e) {
			LOG.error(e);
			throw new BlingProcessingException(
					"Unable to parse bling document: " + filename);
		} catch (org.antlr.runtime.RecognitionException e) {
			LOG.error(e);
			throw new BlingProcessingException(
					"Unable to parse bling document: " + filename);
		}
	}

	private Response performAction(Command command)
			throws BlingProcessingException, BlingRenderException,
			BlingParserException {

		ApplicationController controller;
		try {
			controller = (ApplicationController) command.getController()
					.newInstance();
			String method = command.getMethodName();

			LOG.debug("Attempting to execute method " + method);

			// Prior to invoking the method, set the params
			// from the current Shell, if any
			ApplicationContext appContext = ApplicationContext.getInstance();
			Display display = (Display) appContext
					.getAttribute(Constants.ApplicationContextAttributes.DISPLAY);
			if (display != null) {
				Shell shell = display.getActiveShell();
				if (shell != null) {
					Map params = getParamsFromShell(shell);

					if (controller instanceof ApplicationController) {
						((ApplicationController) controller).params = params;
					}
				}
			}

			GormStandalone gsm = GormStandalone.getInstance();

			Object retval = gsm
					.executeMethodWithTransaction(controller, method);

			if (retval != null && retval instanceof Action) {
				method = ((Action) retval).getPath();

				if (retval instanceof Render) {
					if (!method.contains("/")) {
						method = command.getControllerName() + "/" + method;
					}
					Request newRequest = new Request(method);

					process(newRequest);
					return null;
				}

				if (retval instanceof Partial) {

					if (display != null) {
						Shell shell = display.getActiveShell();

						// Do best to update screen based on context
						Iterator<Control> textBoxes = Util.getChildrenByType(
								shell, Text.class).iterator();
						while (textBoxes.hasNext()) {
							Text textBox = (Text) textBoxes.next();

							String property = (String) textBox.getData();

							if (property != null) {

								Object obj = controller.request.get(property);
								if (obj != null) {
									String value = obj.toString();
									textBox.setText(value);
								}
							}
						}
					}

					return null;
				}
			}

			// FIXME: Use metaClass to add functionality
			// FIXME: If retval != null, then return that name instead
			// return method.getName();
			return new Response(method, controller.request);
		} catch (InstantiationException e) {
			throw new BlingProcessingException(
					"Can not create instance of controller: "
							+ command.getController());
		} catch (IllegalAccessException e) {
			throw new BlingProcessingException("Can not access controller: "
					+ command.getController());
		} catch (IllegalArgumentException e) {
			throw new BlingProcessingException("Can not execute method "
					+ command.getMethod() + " on controller "
					+ command.getController());
		}
	}

	private Map getParamsFromShell(Shell shell) {
		Map retval = new HashMap();

		Control[] children = shell.getChildren();
		retval.putAll(getParamsFromControls(children));

		return retval;
	}

	private Map getParamsFromControls(Control[] children) {
		Map retval = new HashMap();
		for (int i = 0; i < children.length; i++) {
			Control c = children[i];
			if (c instanceof Text) {
				Text t = (Text) c;
				retval.put(t.getData(), t.getText());
			}
			if (c instanceof Group) {
				Group g = (Group) c;
				retval.putAll(getParamsFromControls(g.getChildren()));
			}
			if (c instanceof Combo) {
				Combo t = (Combo) c;
				retval.put(t.getData(), t.getText());
			}
		}
		return retval;
	}

}
